﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Flagwind.Diagnostics;

namespace Flagwind.Diagnostics
{
	public abstract class RemoteLogger : ILogger
	{
		#region 成员字段

		private string _appId;
		private string _postUrl;
		private readonly object _syncRoot;
		private readonly ConcurrentQueue<RemoteLogEntry> _queue;

		#endregion

		#region 构造方法

		protected RemoteLogger()
		{
			_syncRoot = new object();
			_queue = new ConcurrentQueue<RemoteLogEntry>();
		}

		protected RemoteLogger(string appId, string postUrl)
		{
			_appId = appId.Trim();
			_postUrl = postUrl.Trim();
			_syncRoot = new object();
			_queue = new ConcurrentQueue<RemoteLogEntry>();
		}

		#endregion

		#region 公共属性

		public string AppId
		{
			get
			{
				return _appId;
			}
			set
			{
				_appId = value;
			}
		}

		public string PostUrl
		{
			get
			{
				return _postUrl;
			}
			set
			{
				_postUrl = value;
			}
		}

		#endregion

		#region 日志方法

		public void Log(LogEntry entry)
		{
			if(entry == null)
			{
				return;
			}

			if(string.IsNullOrWhiteSpace(PostUrl))
				throw new InvalidOperationException("postUrl is null.");

			var remoteLog = this.ResolveSequence(entry);

			//将日志实体加入内存队列中
			_queue.Enqueue(remoteLog);

			//从线程池拉出一个后台线程进行具体的日志记录操作
			ThreadPool.QueueUserWorkItem(this.LogRemote, entry);
		}

		private void LogRemote(object entity)
		{
			try
			{
				//当前线程获取日志写入锁
				Monitor.Enter(_syncRoot);

				RemoteLogEntry entry;

				//从日志队列中取出一条日志信息
				while(_queue.TryDequeue(out entry))
				{
					//将当前日志信息写入日志文件流
					this.RemoteLog(entry);
				}
			}
			finally
			{
				//如果当前线程是日志写入线程
				if(Monitor.IsEntered(_syncRoot))
				{
					//释放日志写入锁
					Monitor.Exit(_syncRoot);
				}
			}
		}

		#endregion

		#region 抽象方法

		protected abstract void RemoteLog(RemoteLogEntry entry);

		#endregion

		#region 私有方法

		private RemoteLogEntry ResolveSequence(LogEntry entry)
		{
			var entity = new RemoteLogEntry()
			{
				AppId = AppId,
				Level = entry.Level,
				Source = entry.Source,
				Message = entry.Message,
				Exception = entry.ToString(),
				Timestamp = entry.Timestamp
			};

			return entity;
		}

		#endregion
	}
}
